package icasue.reflect.handles.fields;

import icasue.reflect.handles.object.ObjectOF;
import icasue.reflect.utils.ProxyDigestUtil;
import icasue.reflect.exceptions.HandleException;
import icasue.reflect.handles.HandleSupplier;
import icasue.reflect.handles.OFAble;
import icasue.reflect.handles.arrays.AryOF;
import icasue.reflect.handles.classes.ClassOF;
import icasue.reflect.handles.exception.ExceptionOF;
import icasue.reflect.handles.predicate.SureOF;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.util.function.Consumer;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2020/12/3 下午6:57
 * @UpdateDate:
 * @Description:
 */
public class FieldOF implements OFAble {

    /**
     *  setAccessible_  (field)     doc: open field's privileges
     *  get_  (field,invoker)
     *  set_  (field,invoker,val)
     *
     *  findField_ofTp_fName_  (fieldOfType,fName)
     *      doc: find filed in type which you given and supperClass and first interface which type marked.
     *
     *  get_ofTp_fName_instance_  (fieldOfType,fName,invoker)
     *      doc: invoke get method, this operator divide two steps,
     *           first, find field by fOfType and fName which you given,
     *           second, invoke field's get handle by invoker, will throw RuntimeException if field not found.
     *
     *  set_ofTp_fName_instance_val_    (ofType,fName,invoker,val)
     *      doc: invoke set method, this operator divide two steps,
     *           first, find field by fOfType and fName which you given,
     *           second, invoke field's set handle by invoker, will throw RuntimeException if field not found.
     *
     *  get_ofTp_fName_    (ofType,fName)
     *      doc: invoke get method, this operator divide three steps,
     *           first, find field by fOfType and fName which you given,
     *           second, find suitable invoker on environment or create new Instance by construct.
     *           third,invoke field's get handle by invoker, will throw RuntimeException if field not found.
     *           notice: using this method is unSafety, cause of find field has less troubles, but find suitAble invoke by fType is
     *                   unSafe when this type is defined as a interface or supperClass or abstractClass and exists multi implements,
     *                   at this time, findSuitAbleInvoke will using any instance from environment! at this case, wu suggest using
     *                   OFAble.findField_ofTp_fName_ and ObjectOF.findSuitInstance_ and OFAble.get_ to complicate your action safety!
     *
     *  set_ofTp_fName_val_ (ofType,fName,val)
     *      doc: invoke get method, this operator divide three steps,
     *           first, find field by fOfType and fName which you given,
     *           second, find suitable invoker on environment or create new Instance by construct.
     *           third,invoke field's get handle by invoker, will throw RuntimeException if field not found.
     *           notice: using this method is unSafety, cause of find field has less troubles, but find suitAble invoke by fType is
     *                   unSafe when this type is defined as a interface or supperClass or abstractClass and exists multi implements,
     *                   at this time, findSuitAbleInvoke will using any instance from environment! at this case, wu suggest using
     *                   OFAble.findField_ofTp_fName_ and ObjectOF.findSuitInstance_ and OFAble.get_ to complicate your action safety!
     *
     */


    public static Consumer<Field> setAccessible_ = (field) -> {
        try {
            SureOF.notNull_.accept(field);
            if(!ObjectOF.isPublic_.test(field.getModifiers()))
                field.setAccessible(true);
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("FieldOF.setAccessible_ : occur an error -> " , throwable.getMessage());
        }
    };

    /**
     * get accept two params, first as field self, second is invoker.
     * notice get's inst be surely!
     */
    public static HandleSupplier.FunctionAry<Object> get_ = (fieldAndInvoker) -> {
        try {
            SureOF.notNull_.accept(fieldAndInvoker);
            if(fieldAndInvoker.length != 2)
                throw ExceptionOF.handleExcInvocation_.apply("FieldOF.get_ : required 2 params");
            SureOF.notNull_.accept(fieldAndInvoker[0]);
            SureOF.equals_.accept(new Object[]{fieldAndInvoker[0].getClass(),Field.class});
            Field field = Field.class.cast(fieldAndInvoker[0]);
            FieldOF.setAccessible_.accept(field);
            //invoke by getMethod. if getMethod not exist, find a getterHandle.
            try { return FieldO.get.bindTo(field).invoke(fieldAndInvoker[1]); }catch (Throwable throwable){
                try { return MethodHandle.class
                        .cast(FieldO.getterHandle.bindTo(FieldO.lookup).invoke(field))
                        .bindTo(fieldAndInvoker[1]).invoke();  }catch (Throwable t2){
                    throw ExceptionOF.handleExcInvocation_.apply("FieldOF.get_ : can't find field's getter method and getterHandle get error.");
                }
            }
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("FieldOF.get_ : occur an error -> " , throwable.getMessage());
        }
    };


    /**
     * set accept third params, first as field self, second is invoker, third field value to set.
     * notice set's inst be surely!
     */
    public static HandleSupplier.ConsumerAry set_ = (fieldAndInvoker) -> {
        try {
            SureOF.notNull_.accept(fieldAndInvoker);
            if(fieldAndInvoker.length != 3)
                throw ExceptionOF.handleExcInvocation_.apply("FieldOF.set_ : required 3 params");
            SureOF.notNull_.accept(fieldAndInvoker[0]);
            SureOF.equals_.accept(new Object[]{fieldAndInvoker[0].getClass(),Field.class});
            Field field = Field.class.cast(fieldAndInvoker[0]);
            FieldOF.setAccessible_.accept(field);
            //invoke by setMethod. if setMethod not exist, find a setterHandle.
            try { FieldO.set.bindTo(field).invoke(fieldAndInvoker[1],fieldAndInvoker[2]); } catch (Throwable throwable){
                try {  MethodHandle.class
                        .cast(FieldO.setterHandle.bindTo(FieldO.lookup).invoke(field))
                        .bindTo(fieldAndInvoker[1]).invoke(fieldAndInvoker[2]);  }catch (Throwable t2){
                    throw ExceptionOF.handleExcInvocation_.apply("FieldOF.set_ : can't find field's setter method and setterHandle get error.");
                }
            }
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("FieldOF.set_ : occur an error -> " , throwable.getMessage());
        }
    };

    /**
     * find field until top stage interface.
     */
    public static HandleSupplier.FunctionAry<Field> findField_ofTp_fName_ = (ofTp_fName) -> {
        try {
            SureOF.notEmptyAry_.accept(ofTp_fName);
            if(ofTp_fName.length != 2)
                throw ExceptionOF.handleExcInvocation_.apply("FieldOF.findField_ofTp_fName_ : required 2 params");
            SureOF.isCls_.accept(ofTp_fName[0]);
            SureOF.isStr_.accept(ofTp_fName[1]);
            // find field in supperClasses.
            Field target = (Field) ObjectOF.getNull_.get();
            for ( Object sCls = ofTp_fName[0] ; ObjectOF.isNull_.test(target) && ObjectOF.notNull_.test(sCls) ; sCls = ClassOF.getSuperclass_.apply(sCls) ){
                try { target = ClassOF.getDeclaredField_.apply(new Object[]{sCls, ofTp_fName[1]}); }catch (Throwable throwable){
                    target = (Field) ObjectOF.getNull_.get();
                }
            }
            //find field in interfaces.
            if(ObjectOF.isNull_.test(target)) {
                Class[] interfaces = ClassOF.getInterfaces_.apply(ofTp_fName[0]);
                if(ObjectOF.notEmptyAry_.test(interfaces)){
                    for (Class anInterface : interfaces) {
                        try { target = ClassOF.getDeclaredField_.apply(new Object[]{anInterface, ofTp_fName[1]}); }catch (Throwable e){
                            target = (Field) ObjectOF.getNull_.get();
                        }
                        if (ObjectOF.notNull_.test(target))
                            break;
                    }
                }
            }
            if(ObjectOF.isNull_.test(target))
                throw ExceptionOF.handleExcInvocation_.apply("FieldOF.findField_ofTp_fName_ : field not found.");
            return target;
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("FieldOF.findField_ofTp_fName_ : occur an error -> " , throwable.getMessage());
        }
    };


    /**
     * invoke getField by fType,fName,instance.
     */
    public static HandleSupplier.FunctionAry<Object> get_ofTp_fName_instance_ = (ofTp_fName_inst) -> {
        try {
            SureOF.notNull_.accept(ofTp_fName_inst);
            if(ofTp_fName_inst.length != 3)
                throw ExceptionOF.handleExcInvocation_.apply("FieldOF.get_fTp_fName_instance : required 3 params");
            Object[] copy = (Object[]) AryOF.copy_.apply(new Object[]{ofTp_fName_inst, 2});
            Field field = FieldOF.findField_ofTp_fName_.apply(copy);
            Object[] newAry = (Object[])AryOF.create_.apply(new Object[]{Object.class, 2});
            newAry[0] = field; newAry[1] = ProxyDigestUtil.exposeOrigInstanceUnderCglib(ofTp_fName_inst[2]).orElse(null);
            return FieldOF.get_.apply(newAry);
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("FieldOF.get_fTp_fName_instance : occur an error -> " , throwable.getMessage());
        }
    };

    /**
     * invoke setField by fType,fName,instance,value
     */
    public static HandleSupplier.ConsumerAry set_ofTp_fName_instance_val_ = (ofTp_fName_inst_val) -> {
        try {
            SureOF.notNull_.accept(ofTp_fName_inst_val);
            if(ofTp_fName_inst_val.length != 4)
                throw ExceptionOF.handleExcInvocation_.apply("FieldOF.set_ofTp_fName_instance_val_ : required 4 params");
            Object[] copy = (Object[]) AryOF.copy_.apply(new Object[]{ofTp_fName_inst_val, 2});
            Field field = FieldOF.findField_ofTp_fName_.apply(copy);
            Object[] newAry = (Object[]) AryOF.create_.apply(new Object[]{Object.class, 3});
            newAry[0] = field; newAry[2] = ofTp_fName_inst_val[3];
            newAry[1] = ProxyDigestUtil.exposeOrigInstanceUnderCglib(ofTp_fName_inst_val[2]).orElse(null);
            FieldOF.set_.accept(newAry);
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("FieldOF.set_ofTp_fName_instance_val_ : occur an error -> " , throwable.getMessage());
        }
    };

    /**
     * invoke getField by fType,fName.
     */
    public static HandleSupplier.FunctionAry<Object> get_ofTp_fName_ = (ofTp_fName) -> {
        try {
            SureOF.notEmptyAry_.accept(ofTp_fName);
            if(ofTp_fName.length != 2)
                throw ExceptionOF.handleExcInvocation_.apply("FieldOF.get_ofTp_fName_ : required 2 params");
            Object[] copy = (Object[]) AryOF.copy_.apply(new Object[]{ofTp_fName, ofTp_fName.length});
            Field field = FieldOF.findField_ofTp_fName_.apply(copy);
            FieldOF.setAccessible_.accept(field);
            Object suitableInstance = ObjectOF.getNull_.get();
            //find instance in environment, orElse by construct.
            //notice : if tType exists multi instances in environment, invoke will used newInstance ny constructed.
            if(!ObjectOF.isStatic_.test(field.getModifiers()))
                suitableInstance = ObjectOF.findSuitInstance_.apply(ofTp_fName[0]);
            return FieldOF.get_.apply(new Object[]{field,suitableInstance});
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("FieldOF.get_ofTp_fName_ : occur an error -> " , throwable.getMessage());
        }
    };

    /**
     * invoke setField by fType,fName,val
     */
    public static HandleSupplier.ConsumerAry set_ofTp_fName_val_ = (ofTp_fName_val) -> {
        try {
            SureOF.notNull_.accept(ofTp_fName_val);
            if(ofTp_fName_val.length != 3)
                throw ExceptionOF.handleExcInvocation_.apply("FieldOF.set_ofTp_fName_val_ : required 3 params");
            Object[] copy = (Object[]) AryOF.copy_.apply(ofTp_fName_val, 2);
            Field field = FieldOF.findField_ofTp_fName_.apply(copy);
            Object suitableInstance = ObjectOF.getNull_.get();
            //find instance in environment, orElse by construct.
            //notice : if tType exists multi instances in environment, invoke will used newInstance ny constructed.
            if(!ObjectOF.isStatic_.test(field.getModifiers()))
                suitableInstance = ObjectOF.findSuitInstance_.apply(ofTp_fName_val[0]);
            Object[] newAry = (Object[]) AryOF.create_.apply(new Object[]{Object.class, 3});
            newAry[0] = field; newAry[1] = suitableInstance; newAry[2] = ofTp_fName_val[2];
            FieldOF.set_.accept(newAry);
        } catch (HandleException deepError){
            throw deepError;
        } catch (Throwable throwable) {
            throw ExceptionOF.handleExcInvocation_.apply("FieldOF.set_ofTp_fName_val_ : occur an error -> " , throwable.getMessage());
        }
    };


}
